#include "zevent/event.hpp"
#include <zephyr/fff.h>
#include <zephyr/ztest.h>

DEFINE_FFF_GLOBALS;
namespace {

FAKE_VOID_FUNC0(f1);
FAKE_VOID_FUNC0(f2);
FAKE_VOID_FUNC0(f3);

FAKE_VOID_FUNC1(f4, int);
FAKE_VOID_FUNC2(f5, int, int);
struct queue_fixture {
  static auto Setup() -> void * {
    static queue_fixture f;
    return &f;
  }
  static auto Before(void *f) -> void {
    f1_reset();
    f2_reset();
    f3_reset();
    f4_reset();
    f5_reset();
  }
  static auto After(void *f) -> void {
    f1_reset();
    f2_reset();
    f3_reset();
    f4_reset();
    f5_reset();
  }
};

} // namespace

namespace e_basic_op {
ZEVT_TYPE(e1);
ZEVT_TYPE(e2);
} // namespace e_basic_op
ZTEST(queue, basic_operation) {
  using namespace e_basic_op;
  e1::Connect([]() { f1(); });
  e1::Publish();
  zassert_equal(f1_fake.call_count, 1, "%d", f1_fake.call_count);
  e1::Connect([]() { f2(); });
  e1::Connect([]() { f2(); });
  e1::Connect([]() { f3(); });
  e1::Publish();
  zassert_equal(f2_fake.call_count, 2, "%d", f2_fake.call_count);
  zassert_equal(f3_fake.call_count, 1, "%d", f3_fake.call_count);
  zassert_equal(f1_fake.call_count, 2);
}

namespace test_connected {
ZEVT_TYPE(e1);
ZEVT_TYPE(e2);
ZEVT_TYPE(e3);
} // namespace test_connected
ZTEST(queue, test_connected) {
  using namespace test_connected;
  e1::Connect([] { f1(); });
  e2::Connect([] { f2(); });
  e3::Connect([] { f3(); });
  e1::Connect(e2());
  e1::Connect(e2());
  e1::BiConnect(e3());
  e1::Publish();
  zassert_equal(f1_fake.call_count, 1);
  zassert_equal(f2_fake.call_count, 1, "%d", f2_fake.call_count);
  zassert_equal(f3_fake.call_count, 1);
  e2::Publish();
  zassert_equal(f1_fake.call_count, 1);
  zassert_equal(f2_fake.call_count, 2);
  zassert_equal(f3_fake.call_count, 1);
  e3::Publish();
  zassert_equal(f1_fake.call_count, 2);
  zassert_equal(f2_fake.call_count, 2);
  zassert_equal(f3_fake.call_count, 2);
}

namespace e_test_push {
ZEVT_TYPE(e1);
ZEVT_TYPE(e2);
ZEVT_TYPE(e3);

} // namespace e_test_push

ZTEST(queue, test_push) {
  using namespace e_test_push;
  zevent::Queue<5, e1, e2, e3> eq1;
  zevent::Queue<5, e1, e2> eq2;
  zassert_true(eq1.IsEmpty());
  zassert_true(eq2.IsEmpty());
  eq1.Connect<e1>([]() { f1(); });
  e1::Publish();
  zassert_false(eq1.IsEmpty());
  zassert_true(eq2.IsEmpty());
  zassert_equal(f1_fake.call_count, 0);
  eq1.ProcessEvents();
  zassert_equal(f1_fake.call_count, 1);
}

namespace test_push_multi_events {
ZEVT_TYPE(e1);
ZEVT_TYPE(e2);
} // namespace test_push_multi_events

ZTEST(queue, test_push_multi_events) {
  using namespace test_push_multi_events;
  using Queue = zevent::Queue<5, e1, e2>;
  Queue eq;
  e1::Connect([]() { f1(); });
  e2::Connect([]() { f2(); });
  zassert_true(eq.IsEmpty());
  e1::Publish();
  zassert_equal(f1_fake.call_count, 1);
  zassert_false(eq.IsEmpty());
  eq.ProcessEvents();
  zassert_equal(f1_fake.call_count, 1);
  zassert_true(eq.IsEmpty());

  eq.Connect<e1>([] { f3(); });

  e1::Publish();
  zassert_false(eq.IsEmpty());
  zassert_equal(f3_fake.call_count, 0);
  eq.ProcessEvents();
  zassert_equal(f3_fake.call_count, 1);
  zassert_true(eq.IsEmpty());
}

struct S1 {
  ZEVT_TYPE(e1);
  ZEVT_TYPE(e2, int);
  ZEVT_TYPE(e3, int, int);
  auto operator()() {
    using namespace zevent::sml;
    auto a2 = [](const auto &e) {
      const auto value = *e;
      f4(value);
    };
    auto a3 = [](const auto &e) {
      const auto [v1, v2] = *e;
      f5(v1, v2);
    };
    // clang-format off
    return make_transition_table(
      *"s1"_s + zevt<e1> / []{f1();} = "s2"_s 
      ,"s2"_s + zevt<e2>  / a2 = "s3"_s
      ,"s3"_s + zevt<e3> / a3 = X
     );
    // clang-format on
  }
};

ZTEST(queue, sml) {
  using namespace zevent::sml;
  auto &sm1 = zevent::sml::Sm<S1>::Create();
  auto &sm2 = zevent::sml::Sm<S1>::Create();
  zassert_equal(&sm1, &sm2);
  zassert_true(sm1.IsState("s1"_s));
  S1::e1::Publish();
  zassert_true(sm2.IsState("s2"_s));
  S1::e2::Publish(12);
  zassert_equal(f4_fake.arg0_val, 12);
  zassert_true(sm1.IsState("s3"_s));
  S1::e1::Publish();
  zassert_true(sm1.IsState("s3"_s));
  S1::e3::Publish(12, 34);
  zassert_true(sm1.IsState(X));
  zassert_equal(f5_fake.arg0_val, 12);
  zassert_equal(f5_fake.arg1_val, 34);
}

namespace test_queued_sm {
struct SConfig {
  ZEVT_TYPE(e1);
  ZEVT_TYPE(e2, int);
  ZEVT_TYPE(e3, int, int);
  auto operator()() {
    using namespace zevent::sml;
    auto a1 = [] {
      puts("action1\n");
      f2();
    };
    auto a2 = [](const auto &e) {
      const auto value = *e;
      f4(value);
    };
    auto a3 = [](const auto &e) {
      const auto [v1, v2] = *e;
      f5(v1, v2);
    };
    // clang-format off
    return make_transition_table(
      *"s1"_s + zevt<e1> / a1 = "s2"_s 
      ,"s2"_s + zevt<e2> / a2 = "s3"_s
      ,"s3"_s + zevt<e3> / a3 = X
     );
    // clang-format on
  }
};
} // namespace test_queued_sm

ZTEST(queue, test_queued_sm) {
  using namespace test_queued_sm;
  using namespace zevent::sml;

  auto &sm1 = Sm<SConfig>::Create<5>();
  auto &sm2 = Sm<SConfig>::Create<5>();
  zassert_equal_ptr(&sm1, &sm2);
  zassert_true(sm1.IsState("s1"_s));
  SConfig::e1::Publish();
  SConfig::e1::Publish();
  SConfig::e2::Publish(666);
  SConfig::e2::Publish(666);
  SConfig::e3::Publish(6, 6);
  zassert_true(sm1.IsState("s1"_s));
  sm1.ProcessEvents();
  zassert_equal(f2_fake.call_count, 1, "%d", f2_fake.call_count);
  zassert_true(sm1.IsState(X));
}
ZTEST_SUITE(queue, NULL, queue_fixture::Setup, queue_fixture::Before,
            queue_fixture::After, NULL);